﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace IRC
{
    public delegate void MessageReceivedHandler(string message);
    public delegate void SocketConnectedHandler();

    class TcpSocketClient
    {
        public event MessageReceivedHandler OnMessageReceived;
        public event SocketConnectedHandler OnConnected;
        
        private Socket _clientSocket;

        public void StartClient(string serverAddress, int port)
        {
            IPHostEntry ipHostInfo = Dns.GetHostEntry(serverAddress);
            IPAddress ipAddress = ipHostInfo.AddressList[1];
            IPEndPoint remoteEp = new IPEndPoint(ipAddress, port);

            _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Connect to the remote endpoint.
            _clientSocket.BeginConnect(remoteEp, ConnectCallback, _clientSocket);
        }

        private bool IsConnected()
        {
            try
            {
                return !(_clientSocket.Poll(1, SelectMode.SelectRead) && _clientSocket.Available == 0);
            }
            catch (SocketException) { return false; }
        }

        private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                Socket client = (Socket)ar.AsyncState;
                client.EndConnect(ar);

                Console.WriteLine("Socket connected to {0}",
                    client.RemoteEndPoint);

                OnConnected();
                Receive(client);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private void Receive(Socket client)
        {
            if (!IsConnected())
                return;

            try
            {
                // Create the state object.
                StateObject state = new StateObject();
                state.WorkSocket = client;

                // Begin receiving the data from the remote device.
                client.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0,
                    ReceiveCallback, state);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        private void ReceiveCallback(IAsyncResult ar)
        {
            if (!IsConnected())
                return;
            try
            {
                // Retrieve the state object and the client socket 
                // from the asynchronous state object.
                StateObject state = (StateObject)ar.AsyncState;
                Socket client = state.WorkSocket;

                // Read data from the remote device.
                int bytesRead = client.EndReceive(ar);

                var tempS = Encoding.ASCII.GetString(state.Buffer, 0, bytesRead);
                state.sb.Append(tempS);
                
                var messages = Regex.Split(state.sb.ToString(), "\r\n").ToList();
                //
                //Console.WriteLine(tempS);

                state.sb.Clear();

                if (!(state.sb.ToString().EndsWith("\r\n")))
                {
                    state.sb.Append(messages.Last());
                    messages.RemoveAt(messages.Count-1);
                }

                foreach (var message in messages)
                    OnMessageReceived(message);

                // Get the rest of the data.
                client.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0,
                    ReceiveCallback, state);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        public void Send(String data)
        {
            byte[] byteData = Encoding.ASCII.GetBytes(data + Environment.NewLine);

            _clientSocket.BeginSend(byteData, 0, byteData.Length, SocketFlags.None,
                SendCallback, _clientSocket);
        }

        private void SendCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                Socket client = (Socket)ar.AsyncState;

                // Complete sending the data to the remote device.
                int bytesSent = client.EndSend(ar);
                Console.WriteLine("Sent {0} bytes to server.", bytesSent);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }
}
